﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Xml;
using limpalex.bbparser.BuiltInEvaluators;
using limpalex.bbparser.Interfaces;

namespace limpalex.bbparser.Converters
{
    class EvaluatorConverter : IValueConverter
    {
        public object Convert(XmlNode value)
        {
            foreach (XmlNode node in value.ChildNodes)
            {
                if (node.NodeType != XmlNodeType.Element) continue;
                switch (node.Name)
                {
                    case "EvaluatorFactory":
                        return ProcessEvaluatorFactory(node);
                    case "EvaluatorType":
                        return ProcessEvaluatorType(node);
                    case "EvaluatorMethod":
                        return ProcessEvaluatorMethod(node);
                    case "BuiltinEvaluator":
                        return BuiltinEvaluatorsFactory.Create(node.InnerText);
                    default:
                        return null;
                }
            }
            return null;
        }

        private static object ProcessEvaluatorMethod(XmlNode node)
        {
            string typeName = null, methodName = null;
            string[] supportedAttributes = null;
            var isStatic = node.Attributes["static"].Value.Equals("true", StringComparison.OrdinalIgnoreCase);

            foreach (XmlNode child in node.ChildNodes)
            {
                if ( child.NodeType != XmlNodeType.Element)
                    continue;
                switch ( child.Name.ToUpper() )
                {
                    case "TYPE":
                        typeName = child.InnerText;
                        break;
                    case "METHOD":
                        methodName = child.InnerText;
                        break;
                    case "ATTRIBUTESLIST":
                        supportedAttributes = 
                            child.InnerText.Split(new[] {',', ';'}, StringSplitOptions.RemoveEmptyEntries);
                        break;
                    case "ATTRIBUTES":
                        supportedAttributes = GetAttributesList(child);
                        break;
                }
            }

            if (typeName == null || methodName == null || supportedAttributes == null)
                throw new ArgumentException();

            return new MethodAttributeEvaluator(typeName, methodName, supportedAttributes, isStatic);
        }

        private static string[] GetAttributesList(XmlNode child)
        {
            var attrs = new List<string>();

            foreach (XmlNode childNode in child.ChildNodes)
            {
                if ( childNode.NodeType != XmlNodeType.Element)
                    continue;
                attrs.Add(childNode.InnerText);
            }

            return attrs.ToArray();
        }

        private static object ProcessEvaluatorType(XmlNode node)
        {
            var type = Type.GetType(node.InnerText);
            if ( ! Array.Exists(type.GetInterfaces(), iface => iface == typeof(IAttributeEvaluator)))
            {
                throw new ArgumentException("type does not implement IAttributeEvaluator interface");
            }
            return Activator.CreateInstance(type);
        }

        private static object ProcessEvaluatorFactory(XmlNode node)
        {
            var isStatic = node.Attributes["method"].Value.Equals("static", StringComparison.OrdinalIgnoreCase);
            string typeName = null, methodName = null;

            foreach (XmlNode xmlNode in node.ChildNodes)
            {
                if ( xmlNode.NodeType != XmlNodeType.Element)    
                    continue;
                if ( xmlNode.Name.Equals("type", StringComparison.OrdinalIgnoreCase))
                {
                    typeName = xmlNode.InnerText;
                }

                if (xmlNode.Name.Equals("method", StringComparison.OrdinalIgnoreCase))
                {
                    methodName = xmlNode.InnerText;
                }
            }

            if ( methodName == null || typeName == null )
                return null;

            var type = Type.GetType(typeName);
            MethodInfo methodInfo;

            if ( isStatic )
            {
                methodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);
                return methodInfo.Invoke(null, new object[] { });
            }
            else
            {
                methodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance);
                var obj = Activator.CreateInstance(type);
                return methodInfo.Invoke(obj, new object[] { });
            }
        }

        public object ConvertFromString(string value)
        {
            return null;
        }


        private class MethodAttributeEvaluator : IAttributeEvaluator
        {
            private readonly Type _type;
            private readonly MethodInfo _method;
            private readonly string[] _attributes;
            private readonly bool _isStatic;

            private object _cachedObj;

            public string EvaluateAttribute(IParameterList attributes, string name)
            {
                if ( _isStatic )
                {
                    return (string) _method.Invoke(null, new object[] { attributes, name});
                }
                else
                {
                    if (_cachedObj == null)
                        _cachedObj = Activator.CreateInstance(_type);
                    return (string) _method.Invoke(_cachedObj, new object[] {attributes, name});
                }
            }

            public string[] SupportedAttributes
            {
                get 
                { 
                    var array = new string[_attributes.Length]; 
                    Array.Copy(_attributes, array, _attributes.Length);
                    return array; 
                }
            }

            public MethodAttributeEvaluator(string typeName, string methodName, string[] attributes, bool isStatic)
            {
                _attributes = attributes;
                _type = Type.GetType(typeName);
                _method = _type.GetMethod(methodName, CreateBindingFlags(isStatic),
                    null, new[] { typeof(IParameterList), typeof(string) }, null);
                _isStatic = isStatic;
            }

            private static BindingFlags CreateBindingFlags(bool isStatic)
            {
                return BindingFlags.Public | ( isStatic ? BindingFlags.Static : BindingFlags.Instance);
            }
        }
    }
}
